/*
 * Copyright (c) 2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "ShaderManager.h"
#if !defined(ALKES_GLES)
#include "alkes/opengl/GLProgram.h"
#include "alkes/opengl/GLShader.h"
#endif

namespace alkes {

#if !defined(ALKES_GLES)

#include "DefaultShader.inl"

class GLShaderManager
    : public ShaderManager
{
public:

    GLShaderManager();
    virtual ~GLShaderManager();

    virtual bool setShaderProgram(Program* shader_program);
    virtual GLProgram* getProgram();

    virtual void enableVertexAttribArray(GLuint index);
    virtual void disableVertexAttribArray(GLuint index);
    virtual void vertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer);

    virtual GLint getAttribLocation(const GLchar* name);
    virtual GLint getUniformLocation(const GLchar* name);
    virtual void setMatrix(const GLfloat* matrix);

private:
    intrusive_ptr<GLProgram> program_;
    intrusive_ptr<GLProgram> default_program_;
};

#define CreateShaderManager(support_shader) \
    (support_shader) ? new GLShaderManager() : new ShaderManager()

#else

#define CreateShaderManager(support_shader) \
    new ShaderManager()

#endif

ShaderManager* ShaderManager::create(bool support_shader)
{
    return CreateShaderManager(support_shader);
}

void ShaderManager::destroy(ShaderManager* manager)
{
    delete manager;
}

ShaderManager::ShaderManager()
{
}

ShaderManager::~ShaderManager()
{
}

bool ShaderManager::setShaderProgram(Program* /*shader_program*/)
{
    return false;
}

GLProgram* ShaderManager::getProgram()
{
    return 0;
}

void ShaderManager::enableVertexAttribArray(GLuint /*index*/)
{
}

void ShaderManager::disableVertexAttribArray(GLuint /*index*/)
{
}

void ShaderManager::vertexAttribPointer(
    GLuint /*index*/, GLint /*size*/, GLenum /*type*/, GLboolean /*normalized*/, GLsizei /*stride*/, const GLvoid* /*pointer*/)
{
}

GLint ShaderManager::getAttribLocation(const GLchar* /*name*/)
{
    return -1;
}

GLint ShaderManager::getUniformLocation(const GLchar* /*name*/)
{
    return -1;
}

void ShaderManager::setMatrix(const GLfloat* /*matrix*/)
{
}


#if !defined(ALKES_GLES)

GLShaderManager::GLShaderManager()
: program_(0)
, default_program_(0)
{
    intrusive_ptr<GLShader> vs(GLShader::createVertexShader(DEFAULT_VERTEX_SHADER));
    intrusive_ptr<GLShader> fs(GLShader::createFragmentShader(DEFAULT_FRAGMENT_SHADER));
    default_program_ = GLProgram::create(vs, fs);
    program_ = static_cast<GLProgram*>(default_program_);
    program_->useProgram();

    GLint param;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &param);
    LOGI("GL_MAX_VERTEX_ATTRIBS: %d", param);
}

GLShaderManager::~GLShaderManager()
{
}

bool GLShaderManager::setShaderProgram(Program* shader_program)
{
    if (shader_program == program_)
        return true;

    if (!shader_program)
    {
        program_ = static_cast<GLProgram*>(default_program_);
    }
    else
    {
        GLProgram* program = restrict_cast<GLProgram*>(shader_program);

        if (!program)
            return false;

        program_ = program;
    }

    program_->useProgram();

    return true;
}

GLProgram* GLShaderManager::getProgram()
{
    return program_;
}

void GLShaderManager::enableVertexAttribArray(GLuint index)
{
    AL_OGL_INVOKE(glEnableVertexAttribArray(index));
}

void GLShaderManager::disableVertexAttribArray(GLuint index)
{
    AL_OGL_INVOKE(glDisableVertexAttribArray(index));
}

void GLShaderManager::vertexAttribPointer(
    GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)
{
    AL_OGL_INVOKE(glVertexAttribPointer(index, size, type, normalized, stride, pointer));
}

GLint GLShaderManager::getAttribLocation(const GLchar* name)
{
    return glGetAttribLocation(program_->getProgram(), name);
}

GLint GLShaderManager::getUniformLocation(const GLchar* name)
{
    return glGetUniformLocation(program_->getProgram(), name);
}

void GLShaderManager::setMatrix(const GLfloat* matrix)
{
    GLint index = program_->getMatrixLocation();
    if (-1 != index)
    {
        AL_OGL_INVOKE(glUniformMatrix4fv(index, 1, GL_FALSE, matrix));
    }
}

#endif

}
